//
//  Copyright 2015 XiaoJSoft Studio.
//  Copyright 2015 Harbin Institute of Technology(HIT), Software Engineering Department.
//
//  Use of this source code is governed by a proprietary license. You can not read, change or
//  redistribute this source code unless you have a written authorization from the copyright
//  holder listed above.
//

#include "compiler.h"
#include "codec.h"
#include "emit.h"
#include "graph.h"
#include "manifest.h"
#include "text_file.h"
#include <QDir>
#include <QUrl>
#include <QUrlQuery>
#include <QtGlobal>
#include <string>
#include <map>
#include <vector>
#include <queue>
#include <time.h>

namespace ui {
namespace compiler {

//  Private declares.
static bool parsePackageName(const std::string &package_name, std::vector<std::string> &output);
static bool changeToPackageFolder(const QDir &root_dir, const std::string &package_name, QDir &output, std::string &path);
static bool changeToRelativeCSSFolder(const std::string &package_name, std::string &output);

static bool parsePackageName(const std::string &package_name, std::vector<std::string> &output) {
    //  Clear output container.
    output.clear();

    //  Zero length package name is allowed.
    if (package_name.length() == 0) {
        return true;
    }

    //  Initialize the buffer and the C-styled package name.
    std::string buffer = "";
    const char *name = package_name.c_str();

    //  Parse.
    for (const char *name_iterator = name; *name_iterator != '\0'; ++name_iterator) {
        if (*name_iterator == '.') {
            //  Check buffer length.
            if (buffer.length() == 0) {
                return false;
            }

            //  Commit.
            output.push_back(buffer);
            buffer = "";
        } else {
            //  Add current character to the buffer.
            buffer += *name_iterator;
        }
    }

    //  Check buffer length.
    if (buffer.length() == 0) {
        return false;
    }

    //  Commit.
    output.push_back(buffer);

    return true;
}

static bool changeToPackageFolder(const QDir &root_dir, const std::string &package_name, QDir &output, std::string &path) {
    //  Parse the package name.
    std::vector<std::string> package_folders;
    if (!parsePackageName(package_name, package_folders)) {
        return false;
    }

    //  Initialize output folder.
    output = QDir(root_dir);

    //  Initialize the path.
    path = "";

    //  Go to inner folders.
    for (std::vector<std::string>::iterator folder_name = package_folders.begin(); folder_name != package_folders.end(); ++folder_name) {
        if (!output.cd(QString::fromStdString(*folder_name))) {
            return false;
        }
        if (path.length() != 0) {
            path += "/";
        }
        path += *folder_name;
    }

    return true;
}

static bool changeToRelativeCSSFolder(const std::string &package_name, std::string &output) {
    //  Parse the package name.
    std::vector<std::string> package_folders;
    if (!parsePackageName(package_name, package_folders)) {
        return false;
    }

    //  Build CSS folder URL.
    if (package_folders.size() == 0) {
        output = "";
    } else {
        bool first = true;
        for (std::vector<std::string>::iterator it = package_folders.begin(); it != package_folders.end(); ++it) {
            if (!first) {
                output += "/";
            } else {
                output = "";
                first = false;
            }
            output += (*it);
        }
    }

    return true;
}

bool compileUI(const std::string &root_package_path, std::string &output) {
    //  Randomize.
    qsrand(time(NULL));

    //  Initialize the root directory and validity.
    QDir root_dir(QString::fromStdString(root_package_path));
    if ((!root_dir.exists()) || (!root_dir.makeAbsolute())) {
        return false;
    }

    //  Initialize the work queue.
    std::queue<std::string> work_queue;
    while(!work_queue.empty()) {
        work_queue.pop();
    }

    //  Add the root package to the tail of the queue.
    work_queue.push("");

    //  Initialize containers.
    std::map<std::string, unsigned int> id_container;
    std::map<unsigned int, std::string> name_container;
    std::map<unsigned int, Manifest> manifest_container;
    std::map<unsigned int, QDir> dir_container;
    std::map<unsigned int, std::string> path_container;
    std::map<unsigned int, std::string> cssfolder_container;
    id_container.clear();
    name_container.clear();
    manifest_container.clear();
    dir_container.clear();
    path_container.clear();
    cssfolder_container.clear();

    //  Initialize the graph vertex allocator.
    unsigned int graph_vertexes_count = 0;

    //  Process the queue.
    while(!work_queue.empty()) {
        //  Pop a package off from the head of the queue.
        std::string current_package = work_queue.front();
        work_queue.pop();

        //  Ignore the package if it had already been processed.
        if (id_container.find(current_package) != id_container.end()) {
            continue;
        }

        //  Get package directory.
        QDir current_dir;
        std::string current_path;
        if (!changeToPackageFolder(root_dir, current_package, current_dir, current_path)) {
            return false;
        }

        //  Get the CSS folder.
        std::string cssfolder;
        if (!changeToRelativeCSSFolder(current_package, cssfolder)) {
            return false;
        }

        //  Get the content of the manifest file.
        std::string raw_manifest;
        if (!textFileRead(current_dir.filePath("package.json").toStdString(), raw_manifest)) {
            return false;
        }

        //  Load the manifest.
        Manifest manifest;
        if (!manifest.loadString(raw_manifest)) {
            return false;
        }

        //  Add all dependencies to the tail of the queue.
        std::vector<std::string> dependencies;
        manifest.getDependencies(dependencies);
        for (std::vector<std::string>::iterator dependency = dependencies.begin(); dependency != dependencies.end(); ++dependency) {
            work_queue.push(*dependency);
        }

        //  Allocate a vertex in the graph and save all information to the containers.
        unsigned int graph_id = (graph_vertexes_count++);
        id_container[current_package] = graph_id;
        name_container[graph_id] = current_package;
        manifest_container[graph_id] = manifest;
        dir_container[graph_id] = current_dir;
        path_container[graph_id] = current_path;
        cssfolder_container[graph_id] = cssfolder;
    }

    //  Build a graph with several disconcrete vertexes.
    DirectedGraph dep_graph(graph_vertexes_count);

    //  Link vertexes according to the dependencies.
    for (std::map<std::string, unsigned int>::iterator name_and_id = id_container.begin(); name_and_id != id_container.end(); ++name_and_id) {
        //  Get package vertex id and its manifest.
        unsigned int current_id = name_and_id->second;
        Manifest current_manifest = manifest_container[current_id];

        //  Get all dependencies.
        std::vector<std::string> dependencies;
        current_manifest.getDependencies(dependencies);

        //  Link from vertexes of all dependencies to current package vertex.
        for (std::vector<std::string>::iterator dependency = dependencies.begin(); dependency != dependencies.end(); ++dependency) {
            dep_graph.addEdge(id_container[*dependency], current_id);
        }
    }

    //  Resolve the load order.
    std::vector<unsigned int> load_order;
    if (!dep_graph.doToposort(load_order)) {
        return false;
    }

    //  Initialize the emitter.
    Emit emitter;

    //  Load packages one by one according to the load order.
    for (std::vector<unsigned int>::iterator current_load = load_order.begin(); current_load != load_order.end(); ++current_load) {
        //  Get the directory object and the manifest.
        QDir current_dir(dir_container[*current_load]);
        std::string current_path = path_container[*current_load];
        std::string cssfolder = cssfolder_container[*current_load];
        Manifest current_manifest = manifest_container[*current_load];

        //  Initialize the file list container.
        std::vector<std::string> file_list;

        //  Emit the pre-run script.
        emitter.addScriptText(current_manifest.getPreRunScript());

        //  Emit the scripts in the package sources list.
        current_manifest.getSources(file_list);
        for (std::vector<std::string>::iterator load_file = file_list.begin(); load_file != file_list.end(); ++load_file) {
            emitter.addScriptFile(current_dir.filePath(QString::fromStdString(*load_file)).toStdString());
        }

        //  Emit the post-run script.
        emitter.addScriptText(current_manifest.getPostRunScript());

        //  Emit the stylesheets.
        current_manifest.getStylesheets(file_list);
        for (std::vector<std::string>::iterator load_file = file_list.begin(); load_file != file_list.end(); ++load_file) {
            //  Emit.
            if (cssfolder.length() == 0) {
                emitter.addCSSFile(*load_file);
            } else {
                emitter.addCSSFile(cssfolder + "/" + *load_file);
            }
        }
    }

    //  Generate the HTML.
    return emitter.getCode(output);
}

}  //  namespace compiler
}  //  namespace ui
